package com.atguigu.tingshu.common.aspect;

import com.atguigu.tingshu.common.annotation.TingShuCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.util.Random;
import java.util.concurrent.TimeUnit;

@Aspect
@Component
public class TingShuCacheAspect {
    @Resource
    private RedisTemplate<String, Object> redisTemplate;
    @Resource
    private RedissonClient redissonClient;

    @Around("@annotation(tingShuCache)")
    public Object around(ProceedingJoinPoint joinPoint, TingShuCache tingShuCache) throws Throwable {
        //获取缓存的key的前缀
        String prefix = tingShuCache.cachePrefix();
        //获取目标方法的参数
        Object[] args = joinPoint.getArgs();
        String param = StringUtils.join(args, ",");
        String cacheKey = prefix + param;
        //先查redis
        Object result = redisTemplate.opsForValue().get(cacheKey);
        //判断redis中是否有缓存，如果有则直接返回
        if (result != null) return result;
        //如果没有则使用bloom过滤器，防止缓存穿透
        RBloomFilter<Object> rBloomFilter = redissonClient.getBloomFilter(tingShuCache.bloomFilter());
        if (!rBloomFilter.contains(cacheKey)) return null;
        //添加分布式锁，防止缓存击穿
        RLock rLock = redissonClient.getLock(tingShuCache.lockPrefix() + param);
        rLock.lock();
        try {
            //再查redis
            result = redisTemplate.opsForValue().get(cacheKey);
            //判断redis中是否有缓存，如果有则直接返回
            if (result != null) return result;
            //执行目标方法
            result = joinPoint.proceed(args);
            //将结构缓存到redis
            long timeout = tingShuCache.timeout() + new Random().nextLong(tingShuCache.random());
            if (result == null) {
                this.redisTemplate.opsForValue().set(cacheKey, result, tingShuCache.nullTimeout(), TimeUnit.SECONDS);
            } else {
                this.redisTemplate.opsForValue().set(cacheKey, result, timeout, TimeUnit.SECONDS);
            }
            //返回结果
            return result;
        } finally {
            rLock.unlock();
        }
    }
}
